Wingsuit Flight Motion Tracking¶
content
- 1.Import Libraries
- 2.Load Video & Extract Frames
-- 2.1 Load Video
-- 2.2 Extract key video properties
-- 2.3 Extract and store frames
-- 2.4 Release and display - 3.Dynamic Motion Tracking with Optical Flow
-- 3.1 Define feature detection parameters
-- 3.2 Define LK optical flow parameters
-- 3.3 Track features and draw color-doded motion vectors - Generate & Display Animation
- Acknowledgment
In [349]:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import os
import time
import matplotlib.animation as animation
In [350]:
video_cap=cv2.VideoCapture("wingsuit flight.mp4")
In [351]:
#Extract file size
file_size=os.path.getsize("wingsuit flight.mp4") / (1024 * 1024) # file size in MB
#Extract dimension
frame_width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#Extract total frames
total_frames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT))
#Extract fps
frame_fps=video_cap.get(cv2.CAP_PROP_FPS)
#Extract duration
duration_sec = total_frames / frame_fps if frame_fps else 0
duration_min = duration_sec / 60
#Extract codec
codec = int(video_cap.get(cv2.CAP_PROP_FOURCC))
codec_str = "".join([chr((codec >> 8 * i) & 0xFF) for i in range(4)])
#Display video properties
print(f"File Size: {file_size:.2f} MB")
print(f"Dimensions: {frame_width}x{frame_height}")
print(f"Total Frames: {total_frames}")
print(f"FPS: {frame_fps}")
print(f"Duration: {duration_min:.2f} min({duration_sec:.2f})s ")
print(f"Codec: {codec_str}")
File Size: 1.43 MB Dimensions: 640x360 Total Frames: 92 FPS: 9.0 Duration: 0.17 min(10.22)s Codec: h264
In [352]:
#Extract frames
frames_ori = []
frames_gray=[]
frames_max=90
for i in range(min(frames_max,total_frames)):
re,frame=video_cap.read()
if not re:
break
frames_ori.append(frame)
frames_gray.append(cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY))
In [353]:
#Release video capture object
video_cap.release()
#Count frames
print(f"Extracted {len(frames_ori)} frames.")
#Display first frame
frames_ori[0]=cv2.cvtColor(frames_ori[0],cv2.COLOR_BGR2RGB)
plt.imshow(frames_ori[0])
plt.axis('off')
plt.show()
Extracted 90 frames.
In [354]:
feature_params = dict(
maxCorners=200,
qualityLevel=0.01,
minDistance=10,
blockSize=15
)
In [355]:
lk_params = dict(
winSize=(25, 25),
maxLevel=5,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 30, 0.01)
)
In [356]:
#Detect features in the first frame
p0 = cv2.goodFeaturesToTrack(frames_gray[0], mask=None, **feature_params)
p0 = p0.astype(np.float32)
pre=p0
#Define a function to remove duplicate points
def remove_duplicates(existing_points, new_points, min_distance=5):
if existing_points is None or len(existing_points) == 0:
return new_points
if new_points is None or len(new_points) == 0:
return np.empty((0, 1, 2), dtype=np.float32)
existing_points = existing_points.reshape(-1, 2)
new_points = new_points.reshape(-1, 2)
filtered_new_points = []
for new_pt in new_points:
distances = np.linalg.norm(existing_points - new_pt, axis=1)
if np.all(distances >= min_distance):
filtered_new_points.append(new_pt)
return np.array(filtered_new_points)
track_frames=[]
for i in range(1, len(frames_gray)):
p_cur,status,err=cv2.calcOpticalFlowPyrLK(frames_gray[i-1],frames_gray[i],pre,None,**lk_params)
good_new=p_cur[status==1]
good_old=pre[status==1]
print(f"Frame {i}: Tracked points: {len(good_new)}")
if len(good_new)<=50 or (i+1)%5==0:
if(len(good_new)>50):
p_new=cv2.goodFeaturesToTrack(frames_gray[i], mask=None, **feature_params)
if p_new is not None:
p_new=remove_duplicates(good_new,p_new)
good_new=np.vstack((good_new,p_new))
if len(good_new)>200:
good_new=good_new[:200]
else:
while(len(good_new)<=50):
p_new=cv2.goodFeaturesToTrack(frames_gray[i], mask=None, **feature_params)
if p_new is None:
break
p_new=remove_duplicates(good_new,p_new)
if len(p_new)==0:
break
good_new=np.vstack((good_new,p_new))
if len(good_new)>200:
good_new=good_new[:200]
print(f"Frame {i}: Added new points, total now: {len(good_new)}")
frame=frames_ori[i].copy()
#Convert to color for visualization
for new,old in zip(good_new,good_old):
a,b = new.ravel()
c,d = old.ravel()
dx=a-c
dy=b-d
speed=np.sqrt(dx**2+dy**2)
if speed<5:
color=(255,0,0) #Blue for slow
elif speed<15:
color=(0,255,0) #Green for medium
else:
color=(0,0,255) #Red for fast
frame=cv2.arrowedLine(frame, (int(c),int(d)), (int(a),int(b)), color, 2, tipLength=0.3)#Draw arrowed line
track_frames.append(frame)
pre=good_new.reshape(-1,1,2)
Frame 1: Tracked points: 193 Frame 2: Tracked points: 185 Frame 3: Tracked points: 151 Frame 4: Tracked points: 150 Frame 4: Added new points, total now: 200 Frame 5: Tracked points: 199 Frame 6: Tracked points: 199 Frame 7: Tracked points: 196 Frame 8: Tracked points: 192 Frame 9: Tracked points: 192 Frame 9: Added new points, total now: 200 Frame 10: Tracked points: 200 Frame 11: Tracked points: 200 Frame 12: Tracked points: 199 Frame 13: Tracked points: 196 Frame 14: Tracked points: 192 Frame 14: Added new points, total now: 200 Frame 15: Tracked points: 196 Frame 16: Tracked points: 130 Frame 17: Tracked points: 122 Frame 18: Tracked points: 83 Frame 19: Tracked points: 80 Frame 19: Added new points, total now: 200 Frame 20: Tracked points: 197 Frame 21: Tracked points: 193 Frame 22: Tracked points: 170 Frame 23: Tracked points: 120 Frame 24: Tracked points: 103 Frame 24: Added new points, total now: 200 Frame 25: Tracked points: 184 Frame 26: Tracked points: 182 Frame 27: Tracked points: 167 Frame 28: Tracked points: 141 Frame 29: Tracked points: 137 Frame 29: Added new points, total now: 200 Frame 30: Tracked points: 169 Frame 31: Tracked points: 160 Frame 32: Tracked points: 158 Frame 33: Tracked points: 137 Frame 34: Tracked points: 135 Frame 34: Added new points, total now: 200 Frame 35: Tracked points: 189 Frame 36: Tracked points: 183 Frame 37: Tracked points: 175 Frame 38: Tracked points: 171 Frame 39: Tracked points: 163 Frame 39: Added new points, total now: 200 Frame 40: Tracked points: 186 Frame 41: Tracked points: 169 Frame 42: Tracked points: 168 Frame 43: Tracked points: 166 Frame 44: Tracked points: 164 Frame 44: Added new points, total now: 200 Frame 45: Tracked points: 196 Frame 46: Tracked points: 192 Frame 47: Tracked points: 190 Frame 48: Tracked points: 180 Frame 49: Tracked points: 167 Frame 49: Added new points, total now: 200 Frame 50: Tracked points: 194 Frame 51: Tracked points: 187 Frame 52: Tracked points: 170 Frame 53: Tracked points: 166 Frame 54: Tracked points: 165 Frame 54: Added new points, total now: 200 Frame 55: Tracked points: 199 Frame 56: Tracked points: 168 Frame 57: Tracked points: 165 Frame 58: Tracked points: 161 Frame 59: Tracked points: 158 Frame 59: Added new points, total now: 200 Frame 60: Tracked points: 198 Frame 61: Tracked points: 188 Frame 62: Tracked points: 164 Frame 63: Tracked points: 148 Frame 64: Tracked points: 138 Frame 64: Added new points, total now: 200 Frame 65: Tracked points: 191 Frame 66: Tracked points: 182 Frame 67: Tracked points: 179 Frame 68: Tracked points: 166 Frame 69: Tracked points: 161 Frame 69: Added new points, total now: 200 Frame 70: Tracked points: 185 Frame 71: Tracked points: 180 Frame 72: Tracked points: 169 Frame 73: Tracked points: 166 Frame 74: Tracked points: 159 Frame 74: Added new points, total now: 200 Frame 75: Tracked points: 196 Frame 76: Tracked points: 184 Frame 77: Tracked points: 172 Frame 78: Tracked points: 166 Frame 79: Tracked points: 161 Frame 79: Added new points, total now: 200 Frame 80: Tracked points: 195 Frame 81: Tracked points: 188 Frame 82: Tracked points: 172 Frame 83: Tracked points: 169 Frame 84: Tracked points: 166 Frame 84: Added new points, total now: 200 Frame 85: Tracked points: 197 Frame 86: Tracked points: 187 Frame 87: Tracked points: 184 Frame 88: Tracked points: 169 Frame 89: Tracked points: 160 Frame 89: Added new points, total now: 200
In [357]:
def update(frame): #Define update function for animation
plt.cla()
frame_img=cv2.cvtColor(track_frames[frame],cv2.COLOR_BGR2RGB)
img=plt.imshow(frame_img)
plt.axis('off' )
plt.title(f'Frame {frame+1}\n(Red = Fast Speed, Green=Medium Speed,Blue = Slow Speed)',fontsize=16)
return []
#Create animation
plt.close('all')
fig=plt.figure(figsize=(10,6))
ani = animation.FuncAnimation(
fig, update,
frames=range(len(track_frames)),
interval=1000/frame_fps,
blit=True,
repeat=True
)
display(HTML(ani.to_html5_video() ))
ani.save('Wingsuit_Analysis.mp4', fps=frame_fps,codec='libx264')# Save animation as MP4
plt.close(fig)
The video footage is sourced from the YouTube video 'GoPro: Graham Dickinson's Insane Wingsuit Flight - Front Helmet Cam 2 of 3' (URL:https://www.youtube.com/watch?v=kTzwvyzzfjk). This material is used for educational purposes only, with no copyright infringement intended.